/**
 * 
 */
package com.I9lou.se.lurker.index.support;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.regex.Pattern;

import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.LogByteSizeMergePolicy;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.I9lou.se.lurker.analyzer.AnalyzerFactory;
import com.I9lou.se.lurker.cache.ServiceCache;
import com.I9lou.se.lurker.index.IndexWriterFactory;

/**
 * IndexWriter实例工厂实现类
 * 
 * 注意：由该工厂获取的IndexWriter实例不可直接在外部调用IndexWriter#close()来进行关闭操作，
 * 关闭操作应调用工厂{@link #close(String)}方法进行。
 * 
 * 注意：索引文件夹名由两部分组成，并用"-"符号进行分割，第一部分为搜索应用ID，
 * 第二部分为不包含"-"符号的任意合法的文件夹名称字符。该规则不允许被修改，
 * 其他服务可依赖此规则根据索引文件夹名称获取对应的搜索应用ID。 
 * 
 * @author chenwei
 * @date 2011-03-07
 */
public class SimpleIndexWriterFactory implements IndexWriterFactory {
	
	/**
	 * 日志输出
	 */
	private static Logger LOG = LoggerFactory.getLogger(SimpleIndexWriterFactory.class);
	
	/**
	 * 索引文件夹名称正则匹配器
	 */
	private static Pattern INDEX_DIR_NAME_PATTERN = Pattern.compile("^\\d{1,}-.*$");
	
	/**
	 * IndexWriter服务实例缓存池，其中：Key为传入的索引文件夹名称，
	 * Value为该索引文件夹所对应的IndexWriter实例
	 */
	private final ServiceCache<IndexWriter> pool = new ServiceCache<IndexWriter>(this){

		@Override
		public IndexWriter create(String key) {
			return ((SimpleIndexWriterFactory)getTarget()).create(key);
		}
		
	};
	
	/**
	 * 当前不可服务IndexWriter实例缓存Map，如果IndexWriter实例正在进行关闭操作，则会被移入此缓存Map中，
	 * 关闭操作完成后，再从此Map中进行移除。
	 */
	private ConcurrentHashMap<String, IndexWriter> unavailablePool = new ConcurrentHashMap<String, IndexWriter>();
	
	/**
	 * UnavailablePool同步私有锁
	 */
	private Object unavailablePoolLock = new Object();
	
	/**
	 * 索引文件夹所在父文件夹路径
	 */
	private String rootPath = "";
	
	/**
	 * 用于监控外部调度器的锁
	 * 只有当indexWriterLatch计数完毕，才能关闭IndexWriterFactory
	 */
	private CountDownLatch indexWriterLatch;
	
	/**
	 * Analyzer实例工厂
	 */
	private AnalyzerFactory analyzerFactory = null;
	
	/**
	 * 搜索应用的IndexWriter配置信息，该Map的key为搜索应用ID。
	 * 如果没有为指定的应用提供配置，则使用默认配置创建其IndexWriter实例。
	 */
	private Map<Integer, IndexWriterConfig> indexWriterConfigMap = new HashMap<Integer, IndexWriterConfig>();
	
	/**
	 * 设置索引文件夹所在父文件夹路径
	 * @param rootPath
	 */
	public void setRootPath(String rootPath) {
		this.rootPath = rootPath;
	}
	
	/**
	 * 设置Analyzer实例工厂
	 * @param analyzerFactory
	 */
	public void setAnalyzerFactory(AnalyzerFactory analyzerFactory) {
		this.analyzerFactory = analyzerFactory;
	}

	/**
	 * 设置搜索应用的IndexWriter配置信息，该Map的key为搜索应用ID。
	 * 如果没有为指定的应用提供配置，则使用默认配置创建其IndexWriter实例。
	 * @param indexWriterConfigMap
	 */
	public void setIndexWriterConfigMap(Map<Integer, IndexWriterConfig> indexWriterConfigMap) {
		this.indexWriterConfigMap = indexWriterConfigMap;
	}

	@Override
	public ServiceCache<IndexWriter> getPool() {
		return pool;
	}

	@Override
	public CountDownLatch getIndexWriterLatch() {
		return indexWriterLatch;
	}

	@Override
	public IndexWriter get(String indexDirName) {
		return pool.get(indexDirName);
	}

	@Override
	public Set<String> getIndexDirNames() {
		return pool.keySet();
	}

	@Override
	public void close(String indexDirName) {
		long startTime = System.currentTimeMillis();
		ensureValid( indexDirName );
		IndexWriter writer = null;
		synchronized( unavailablePoolLock ) { //将该IndexWriter从正常缓存池中移除，放入不可服务缓存池中
			writer = pool.remove(indexDirName);
			if( writer != null )
				unavailablePool.put(indexDirName, writer);
			else
				writer = unavailablePool.get(indexDirName);
		}
		if( writer != null ) {
			try {
				writer.close();
				unavailablePool.remove(indexDirName);
				LOG.info("Close IndexWriter[{}] success. cost: {}ms", 
						indexDirName, System.currentTimeMillis()-startTime);
			} catch (OutOfMemoryError e) {
				LOG.error("Can't close IndexWriter: " + indexDirName + "! Try again!", e);
				close(indexDirName);
			} catch(Exception e) {
				LOG.error("Unrecoverable error! Can't close IndexWriter: " + indexDirName, e);
			}
		}
	}
	
	public void init() {
		//初始化indexWriter调度器的关闭锁
		indexWriterLatch = new CountDownLatch(1);
	}
	
	/**
	 * 当前工厂销毁函数，通常在服务关闭时调用此方法进行资源释放。
	 */
	public void destroy() {
		try {
			//等待调度器关闭
			indexWriterLatch.await();
		} catch (InterruptedException e) {}
		
		long startTime = System.currentTimeMillis();
		for(String indexDirName : pool.keySet()) {
			close(indexDirName);
		}
		LOG.info("Destroy SimpleIndexWriterFactory over! cost: {}ms", System.currentTimeMillis() - startTime);
	}
	
	/**
	 * 根据索引文件夹名称创建对应的IndexWriter实例
	 * @param indexDirName
	 * @return indexWriter
	 */
	private IndexWriter create(String indexDirName) {
		ensureValid( indexDirName ); //判断需要创建的索引目录名称是否符合命名规则
		
		synchronized( unavailablePoolLock ) { //判断需要创建的索引目录是否正在执行关闭操作，如果是，则返回null
			if( unavailablePool.get(indexDirName) != null ) {
				LOG.warn("Unavailable indexWriter: {}! Wait for close!", indexDirName);
				return null;
			}
		}
		
		try { //执行索引目录创建操作
			long startTime = System.currentTimeMillis();
			IndexWriterConfig config = indexWriterConfigMap.get( getAppId(indexDirName) );
			if( config != null ) {
				config = deepCopy(config);
			}
			IndexWriter writer = null;
			FSDirectory directory = FSDirectory.open( new File(rootPath + indexDirName) );
			if( config == null ) { //如果对应的搜索应用配置不存在，则使用默认配置创建IndexWriter
				IndexWriterConfig writerConfig = new IndexWriterConfig(Version.LUCENE_34, analyzerFactory.getDefault());
				writer =  new IndexWriter(directory, writerConfig.setReaderPooling(true));
				LOG.info("Create IndexWriter[{}] success by default. cost: {}ms.", 
						 indexDirName, System.currentTimeMillis()-startTime);
			} else {
				writer =  new IndexWriter(directory, config.setReaderPooling(true));
				LOG.info("Create IndexWriter[{}] success by specific: {}. cost: {}ms.", 
						new Object[]{indexDirName, config, System.currentTimeMillis()-startTime});
			}
			return writer;
		} catch (Exception e) { //创建失败则返回null
			LOG.error("Can't create IndexWriter for: " + rootPath + indexDirName, e);
			return null;
		}
	}
	
	/**
	 * 检查索引文件夹名称是否符合规则，如果不符合，则抛出IllegalArgumentException异常。
	 * @param indexDirName
	 * @throws IllegalArgumentException
	 */
	private void ensureValid(String indexDirName) {
		if( indexDirName==null || !INDEX_DIR_NAME_PATTERN.matcher(indexDirName).matches() )
			throw new IllegalArgumentException( "Invalid Index Directory Name: " + indexDirName 
				    							+ "!Valid name example: 101-xxx" );
	}
	
	/**
	 * 根据索引文件夹名称获取其对应的
	 * @param indexDirName
	 * @return
	 */
	private int getAppId(String indexDirName) {
		return Integer.valueOf(indexDirName.split("-")[0]);
	}
	
	private IndexWriterConfig deepCopy(IndexWriterConfig target) {
		LogByteSizeMergePolicy mergePolicy = new LogByteSizeMergePolicy();
		mergePolicy.setUseCompoundFile(false);
		mergePolicy.setMergeFactor(10);
		IndexWriterConfig dest = new IndexWriterConfig(Version.LUCENE_34, target.getAnalyzer());
		dest.setRAMBufferSizeMB(64);
		dest.setMergePolicy(mergePolicy);
		return dest;
	}

}
